// Includes
#include "MeshLoaders.h"
#include "FileHelpers.h"		// File helper functions
#include "CStaticMesh.h"		// Static mesh class header

#include <iostream>		// C++ I/O stream library
#include <fstream>		// C++ file stream library
#include <cstring>		// C string library
#include <cstdio>		// C standard I/O library

using namespace std;

#ifdef _WIN32
	#pragma warning (disable : 4996)		// Disable warning for unsafe sscanf() and sprintf()
#endif

namespace EraAstral
{
	using namespace graphics;
	
	// Loads a mesh from an OBJ file
	IMesh *LoadOBJ(const char *fileName)
	{
		// Pointer to new mesh object
		IMesh *retMesh = NULL;
		
		// Load mesh file to memory
		char *data = NULL;
		unsigned int numBytes = LoadFileToMemory(fileName, &data);
		cout << "Mesh file size: " << numBytes << " bytes." << endl;
		
		// Check for error
		if (data == NULL)
		{
			// Print error and return null
			cout << "Error loading mesh." << endl;
			return NULL;
		}
		
		// Variables for vertex, tex coord, normal and index count
		unsigned int vertexCount = 0, texCoordCount = 0, normalCount = 0, indexCount = 0;
		
		// Loop through bytes to determine mesh data size
		for (unsigned int i = 0; i < numBytes; i++)
		{
			// Skip comment line
			if (data[i] == '#')
			{
				while (data[i] != '\n')
					i++;
				i++;
			}

			// Move to next relevant byte
			while (true)		
			{
				if ((data[i] == 'v' || data[i] == 'f') && data[i - 1] == '\n')
				{
					// Relevant byte found
					// Criteria is a v or an f - Also previous character should be a new line char
					break;
				}			
			
				// Move to next byte
				i++;				
				// Check if we reached the end of the file
				if (i == (numBytes - 1))
					break;
			}

			if (strncmp(data + i, "vt", 2) == 0)
			{
				// Texture coordinate declaration
				texCoordCount++;
			}
			else if (strncmp(data + i, "vn", 2) == 0)
			{
				// Normal declaration
				normalCount++;
			}
			else if (strncmp(data + i, "v", 1) == 0)
			{
				// Vertex declaration
				vertexCount++;
			}
			else if (strncmp(data + i, "f", 1) == 0)
			{
				// Face declaration
				indexCount += 3;
			}
		}
		
		// Check if vertex count is 0
		if (vertexCount == 0)
		{
			// Print error and return null
			cout << "Error: Mesh file has no vertices." << endl;
			return NULL;
		}
		
		// Indicates if the mesh has texture coordinates and normals
		bool hasTexCoords = (texCoordCount > 0);
		bool hasNormals = (normalCount > 0);
		
#ifdef _DEBUG
		// Print information about mesh file
		cout << "Mesh file data inspected." << endl;
		cout << "  Vertices: " << vertexCount << endl << "  Normals: " << normalCount << endl <<
				"  Texture Coordinates: " << texCoordCount << endl << "  Indices: " << indexCount << endl;
#endif		
		
		// Allocate memory for mesh data
		Vector3 *vertices = new Vector3[vertexCount];
		Vector3 *normals = NULL;
		if (hasNormals)
			normals = new Vector3[normalCount];
		Vector2 *texCoords = NULL;		
		if (hasTexCoords)
			texCoords = new Vector2[texCoordCount];
		IndexStruct *indices = new IndexStruct[indexCount];
		
		// Pointers to current location in data arrays
		int curVertexPos = 0, curNormalPos = 0, curTexCoordPos = 0, curIndexPos = 0;
		
		// Loop through bytes again to retrieve data
		for (unsigned int i = 0; i < numBytes; i++)
		{
			// Skip comment line
			if (data[i] == '#')
			{
				while (data[i] != '\n')
					i++;
				i++;
			}

			// Move to next relevant byte
			while (true)		
			{
				if ((data[i] == 'v' || data[i] == 'f') && data[i - 1] == '\n')
				{
					// Relevant byte found
					// Criteria is a v or an f - Also previous character should be a new line char
					break;
				}		
			
				// Move to next byte
				i++;				
				// Check if we reached the end of the file
				if (i == (numBytes - 1))
					break;
			}
			
			if (strncmp(data + i, "vt", 2) == 0)
			{
				// Texture coordinate declaration
				sscanf(&data[i], "vt %f %f", &texCoords[curTexCoordPos].X, &texCoords[curTexCoordPos].Y);
				curTexCoordPos++;
			}
			else if (strncmp(data + i, "vn", 2) == 0)
			{
				// Normal declaration
				sscanf(&data[i], "vn %f %f %f", &normals[curNormalPos].X, &normals[curNormalPos].Y, &normals[curNormalPos].Z);				
				curNormalPos++;
			}
			else if (strncmp(&data[i], "v", 1) == 0)
			{
				// Vertex declaration
				sscanf(&data[i], "v %f %f %f", &vertices[curVertexPos].X, &vertices[curVertexPos].Y, &vertices[curVertexPos].Z);
				curVertexPos++;
			}
			else if (strncmp(data + i, "f", 1) == 0)
			{
				// Face declaration
				if (hasTexCoords)
				{
					// Read index data with texture coordinates
					sscanf(&data[i], "f %d/%d/%d %d/%d/%d %d/%d/%d",
										&indices[curIndexPos].vertex, &indices[curIndexPos].texCoord, &indices[curIndexPos].normal,
										&indices[curIndexPos + 1].vertex, &indices[curIndexPos + 1].texCoord, &indices[curIndexPos + 1].normal,
										&indices[curIndexPos + 2].vertex, &indices[curIndexPos + 2].texCoord, &indices[curIndexPos + 2].normal);
				}
				else
				{
					// Read index data without texture coordinates
					sscanf(&data[i], "f %d//%d %d//%d %d//%d",
										&indices[curIndexPos].vertex, &indices[curIndexPos].normal,
										&indices[curIndexPos + 1].vertex, &indices[curIndexPos + 1].normal,
										&indices[curIndexPos + 2].vertex, &indices[curIndexPos + 2].normal);
										
					// Set blank texture coordinates 
					for (int j = 0; j < 3; j++)
						indices[curIndexPos + j].texCoord = 0;
				}				
				
				// Since C++ arrays use 0 based indices and Wavefront files
				//  do not I'm subtracting 1 from all values
				for (int j = 0; j < 3; j++)
				{
					indices[curIndexPos + j].vertex--;
					if (hasNormals)
						indices[curIndexPos + j].normal--;
					if (hasTexCoords)
						indices[curIndexPos + j].texCoord--;
				}
				
				// Skip ahead 3 indices
				curIndexPos += 3;
			}
		}
		
		// Create static mesh
		retMesh = new StaticMesh(&vertices, &normals, &texCoords, &indices, vertexCount, normalCount,
									texCoordCount, indexCount);
		// Check for error
		if (retMesh == NULL)
		{
			// Delete allocated mesh data
			delete[] vertices;
			delete[] normals;
			delete[] texCoords;
			delete[] indices;
		}
		
		// Delete file data
		delete[] data;
				
		return retMesh;
	}
}
